/* @flow */

import config from '../config';

// 使用Proxy代理处理对象
import { initProxy } from './proxy';

// 初始化props / methosd / data / computed / watch;
import { initState } from './state';

// 渲染dom?
import { initRender } from './render';

// 初始化events
import { initEvents } from './events';
import { mark, measure } from '../util/perf';
import { initLifecycle, callHook } from './lifecycle';
import { initProvide, initInjections } from './inject';
import { extend, mergeOptions, formatComponentName } from '../util/index';

let uid = 0;

export function initMixin(Vue: Class<Component>) {
    // 给 Vue 实例增加 _init() 方法。
    // 合并 options / 初始化操作
    // 相当于vue的入口
    Vue.prototype._init = function (options?: Object) {
        //记录当前vue实例
        const vm: Component = this;
        // a uid 标识
        vm._uid = uid++;

        let startTag, endTag;
        /* istanbul ignore if */
        if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
            startTag = `vue-perf-start:${vm._uid}`;
            endTag = `vue-perf-end:${vm._uid}`;
            mark(startTag);
        }

        // a flag to avoid this being observed
        // 标记当前实例为vue实例。后期observe时不需要处理
        vm._isVue = true;
        // merge options
        // _isComponent 是否为组件
        if (options && options._isComponent) {
            // optimize internal component instantiation 优化内部组件实例化
            // since dynamic options merging is pretty slow, and none of the
            // internal component options needs special treatment.
            // 因为动态options数据合并非常慢，而且内部组件options都不需要特殊处理
            initInternalComponent(vm, options);
        } else {
            // 合并 options(将用户传入的options和之前给vue初始化的构造函数options合并)
            vm.$options = mergeOptions(
                // vm.constructor vue的构造函数
                resolveConstructorOptions(vm.constructor),
                options || {},
                vm
            );
        }
        /* istanbul ignore else */
        // 设置renderProxy，设置渲染时候代理
        if (process.env.NODE_ENV !== 'production') {
            initProxy(vm);
        } else {
            // 设置渲染代理对象设置为vue实例
            vm._renderProxy = vm;
        }
        // expose real self
        vm._self = vm;
        // vm 的生命周期相关变量初始化
        // $children/$parent/$root/$refs
        initLifecycle(vm);

        // vm 的事件监听初始化, 父组件绑定在当前组件上的事件
        initEvents(vm);

        // vm 的编译render初始化
        // $slots/$scopedSlots/_c/$createElement/$attrs/$listeners
        initRender(vm);

        // 触发 beforeCreate 生命钩子的回调
        callHook(vm, 'beforeCreate');

        // 把 inject 的成员注入到 vm 上。initInject和initProvide是一对，实现依赖注入的。
        initInjections(vm); // resolve injections before data/props
        // 初始化 vm 的 _props/methods/_data/computed/watch
        initState(vm);
        // 初始化 provide
        initProvide(vm); // resolve provide after data/props
        // created 生命钩子的回调
        callHook(vm, 'created');

        /* istanbul ignore if */
        if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
            vm._name = formatComponentName(vm, false);
            mark(endTag);
            measure(`vue ${vm._name} init`, startTag, endTag);
        }
        // 调用 $mount() 挂载
        if (vm.$options.el) {
            vm.$mount(vm.$options.el);
        }
    };
}
// 初始化内部组件.
export function initInternalComponent(vm: Component, options: InternalComponentOptions) {
    // 将vue实例上构造函数的options都拷贝一份到子组件上
    const opts = (vm.$options = Object.create(vm.constructor.options));
    // doing this because it's faster than dynamic enumeration.
    const parentVnode = options._parentVnode;
    opts.parent = options.parent;
    opts._parentVnode = parentVnode;

    const vnodeComponentOptions = parentVnode.componentOptions;
    opts.propsData = vnodeComponentOptions.propsData;
    opts._parentListeners = vnodeComponentOptions.listeners;
    opts._renderChildren = vnodeComponentOptions.children;
    opts._componentTag = vnodeComponentOptions.tag;

    if (options.render) {
        opts.render = options.render;
        opts.staticRenderFns = options.staticRenderFns;
    }
}

export function resolveConstructorOptions(Ctor: Class<Component>) {
    let options = Ctor.options;
    if (Ctor.super) {
        const superOptions = resolveConstructorOptions(Ctor.super);
        const cachedSuperOptions = Ctor.superOptions;
        if (superOptions !== cachedSuperOptions) {
            // super option changed,
            // need to resolve new options.
            Ctor.superOptions = superOptions;
            // check if there are any late-modified/attached options (#4976)
            const modifiedOptions = resolveModifiedOptions(Ctor);
            // update base extend options
            if (modifiedOptions) {
                extend(Ctor.extendOptions, modifiedOptions);
            }
            options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions);
            if (options.name) {
                options.components[options.name] = Ctor;
            }
        }
    }
    return options;
}

function resolveModifiedOptions(Ctor: Class<Component>): ?Object {
    let modified;
    const latest = Ctor.options;
    const sealed = Ctor.sealedOptions;
    for (const key in latest) {
        if (latest[key] !== sealed[key]) {
            if (!modified) modified = {};
            modified[key] = latest[key];
        }
    }
    return modified;
}
